﻿using System.Collections;
using System.Runtime.CompilerServices;
using System.Text;
using OnDash.Core;
using OnDash.Windowing.Common.Inputs.Enums;
using OnDash.Windowing.GraphicsLibraryFramework.Enums;

namespace OnDash.Windowing.GraphicsLibraryFramework.Inputs;

public class JoystickState
{
    private float[] _axes;
    private float[] _axesPrevious;
    private BitArray _buttons;
    private BitArray _buttonsPrevious;
    private Hat[] _hats;

    private Hat[] _hatsPrevious;

    internal JoystickState(int hatCount, int axesCount, int buttonCount, int id, string name)
    {
        _hats = new Hat[hatCount];
        _axes = new float[axesCount];
        _buttons = new BitArray(buttonCount);

        _hatsPrevious = new Hat[hatCount];
        _axesPrevious = new float[axesCount];
        _buttonsPrevious = new BitArray(buttonCount);

        Id = id;
        Name = name;
    }

    private JoystickState(JoystickState source)
        : this(source._hats.Length, source._axes.Length, source._buttons.Length, source.Id, source.Name)
    {
        Array.Copy(source._hats, _hats, source._hats.Length);
        Array.Copy(source._axes, _axes, source._axes.Length);

        _buttons = (BitArray)source._buttons.Clone();

        Array.Copy(source._hatsPrevious, _hatsPrevious, source._hatsPrevious.Length);
        Array.Copy(source._axesPrevious, _axesPrevious, source._axesPrevious.Length);

        _buttonsPrevious = (BitArray)source._buttonsPrevious.Clone();
    }

    public int Id { get; }

    public string Name { get; }

    public int ButtonCount =>
        _buttons.Length;

    public int AxisCount =>
        _axes.Length;

    public int HatCount =>
        _hats.Length;

    public Hat GetHat(int index) =>
        _hats[index];

    public Hat GetHatPrevious(int index) =>
        _hatsPrevious[index];

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void SetHat(int index, Hat value)
    {
        _hats[index] = value;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool IsButtonDown(int index) =>
        _buttons[index];

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool WasButtonDown(int index) =>
        _buttonsPrevious[index];

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool IsButtonPressed(int index) =>
        IsButtonDown(index) && !WasButtonDown(index);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public bool IsButtonReleased(int index) =>
        !IsButtonDown(index) && WasButtonDown(index);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void SetButtonDown(int index, bool value)
    {
        _buttons[index] = value;
    }

    public float GetAxis(int index) =>
        _axes[index];

    public float GetAxisPrevious(int index) =>
        _axesPrevious[index];

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void SetAxis(int index, float value)
    {
        _axes[index] = value < -1 ? -1 : value > 1 ? 1 : value;
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void SetAxes(Span<float> axes)
    {
        if (axes.Length > _axes.Length)
            _axes = new float[axes.Length];

        axes.CopyTo(_axes);
    }

    public override string ToString()
    {
        var joinedHats = string.Join(", ", _hats);
        var joinedAxes = string.Join(", ", _axes);

        var buttonBuilder = new StringBuilder();

        for (var i = 0; i < _buttons.Length; i++)
            buttonBuilder.Append($", {(IsButtonDown(i) ? "down" : "up")}");

        return $"{{id: {Id}, name: {Name}, hats: [{joinedHats}], axes: [{joinedAxes}], buttons: [{buttonBuilder}]}}";
    }

    internal void Update()
    {
        UpdateHats();
        UpdateAxes();
        UpdateButtons();
    }

    private unsafe void UpdateButtons()
    {
        Utils.Swap(ref _buttons, ref _buttonsPrevious);

        var b = Glfw.GetJoystickButtonsRaw(Id, out var count);
        for (var j = 0; j < count; j++)
            SetButtonDown(j, b[j] == JoystickInputAction.Press);
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    private void UpdateAxes()
    {
        Utils.Swap(ref _axes, ref _axesPrevious);

        var axes = Glfw.GetJoystickAxes(Id);
        SetAxes(axes);
    }

    private unsafe void UpdateHats()
    {
        Utils.Swap(ref _hats, ref _hatsPrevious);

        var h = Glfw.GetJoystickHatsRaw(Id, out var count);
        for (var j = 0; j < count; j++)
            SetHat(j, (Hat)h[j]);
    }

    public JoystickState GetSnapshot() =>
        new(this);
}